package org.javaz.ports;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.*;
import java.util.*;

/**
 */
public class SmartPortsTool
{
	public static ArrayList tokens = new ArrayList();

	static
	{
		tokens.add("-");
		tokens.add(".");
		tokens.add(",");
		tokens.add("_");
	}

	public static void main(String[] args) throws Exception
	{
		if (args.length != 2)
		{
			System.err.println("You must provde two parameters: template file and output file.\n");
			System.err.println("Output file can be stdout, if you use --stdout instead of filename.");
			System.exit(1);
		}
		String template = args[0];
		File templateFile = new File(template);
		if (!templateFile.exists() || templateFile.isDirectory())
		{
			System.err.println("Template file can't be found by filename " + template + ".");
			System.err.println("Maybe typo?.");
			System.exit(1);
		}
		String outFile = args[1];

		String dbDir = "/var/db/pkg";
		String portsDir = "/usr/ports/";
		String indexFile = "INDEX-8";
		String skippedFile = "skippedfile";
		String requiredBy = "+REQUIRED_BY";
		String contents = "+CONTENTS";
		String origin = "@comment ORIGIN:";
		long indexExpireWarning = 604800000l;

		Properties properties = new Properties();
		try
		{
			properties.load(new FileReader("smartports.properties"));
		}
		catch (IOException e)
		{
			//simply ignore this
		}
		if (properties.containsKey("skipped.file"))
		{
			skippedFile = properties.getProperty("skipped.file");
		}
		if (properties.containsKey("db.dir"))
		{
			dbDir = properties.getProperty("db.dir");
		}
		if (properties.containsKey("ports.dir"))
		{
			portsDir = properties.getProperty("ports.dir");
		}
		if (properties.containsKey("index.file"))
		{
			indexFile = properties.getProperty("index.file");
		}
		if (properties.containsKey("index.expire.warning"))
		{
			indexExpireWarning = Long.parseLong(properties.getProperty("index.expire.warning"));
		}

		//normalizing portsdir
		if (!portsDir.endsWith("/"))
		{
			portsDir += "/";
		}

		File index = null;
		if (indexFile.startsWith("/"))
		{
			index = new File(indexFile);
		}
		else
		{
			index = new File(portsDir, indexFile);
		}
		if (!index.exists())
		{
			System.err.println("Index file '" + indexFile + "' can not be found.");
			System.err.println("Edit smartports.properties, and specify your Index file. Absolute paths works too.");
			System.exit(1);
		}

		if (System.currentTimeMillis() - index.lastModified() > indexExpireWarning)
		{
			System.err.println("Index file '" + indexFile + "' too old, maybe update?.");
		}

		Hashtable currentByPortDir = new Hashtable();
		Hashtable installedByPortDirs = new Hashtable();
		Hashtable forUpdate = new Hashtable();
		ArrayList orderedUpdateable = new ArrayList();

		File dir = new File(dbDir);
		if (dir.isDirectory())
		{
			String[] strings = dir.list();
			Arrays.sort(strings);
			for (int i = 0; i < strings.length; i++)
			{
				String string = strings[i];
				PortVersionInfo info = new PortVersionInfo(string);
				//fix port name;
				boolean found = false;
				File cont = new File(new File(dbDir, string), contents);
				if (cont.exists())
				{
					//build required by;
					LineNumberReader reqreader = new LineNumberReader(new FileReader(cont));
					String line = null;
					while (!found && (line = reqreader.readLine()) != null)
					{
						if (line.contains(origin))
						{
							String or = line.substring(line.indexOf(origin) + origin.length());
							found = true;
							info.setName(or);
							info.setPortDir(portsDir + or);
						}
					}
					reqreader.close();

					//let's respect non-ports software
					if (info.getPortDir() != null)
					{
						//find required by;
						File req = new File(new File(dbDir, string), requiredBy);
						if (req.exists())
						{
							//build required by;
							reqreader = new LineNumberReader(new FileReader(req));
							line = null;
							while ((line = reqreader.readLine()) != null)
							{
								if (line.trim().length() > 0)
								{
									info.getRequiredBy().add(line);
								}
							}
							reqreader.close();
						}

						installedByPortDirs.put(info.getPortDir(), info);
					}
				}
			}
		}

		LineNumberReader reader = new LineNumberReader(new FileReader(index));
		String line = null;
		while ((line = reader.readLine()) != null)
		{
			StringTokenizer tokenizer = new StringTokenizer(line, "|");
			//todo correct count;
			//but anyway three works fine
			if (tokenizer.countTokens() > 3)
			{
				String fullname = tokenizer.nextToken();
				PortVersionInfo info = new PortVersionInfo(fullname);
				String portName = tokenizer.nextToken();
				String name = portName.substring(portName.indexOf(portsDir) + portsDir.length());
				info.setName(name);
				info.setPortDir(portName);
				if (installedByPortDirs.containsKey(info.getPortDir()))
				{
					currentByPortDir.put(info.getPortDir(), info);
				}
			}
		}
		reader.close();

		ArrayList installedNames = new ArrayList(installedByPortDirs.keySet());
		Collections.sort(installedNames);
		for (Iterator iterator = installedNames.iterator(); iterator.hasNext();)
		{
			String portDir = (String) iterator.next();
			PortVersionInfo info1 = (PortVersionInfo) installedByPortDirs.get(portDir);
			PortVersionInfo info2 = (PortVersionInfo) currentByPortDir.get(portDir);
			if (info1 != null && info2 != null)
			{
				String v1 = info1.getVersion();
				String v2 = info2.getVersion();
				if (!v1.equals(v2))
				{
					if (newerVersion(v1, v2))
					{
						info2.getRequiredBy().addAll(info1.getRequiredBy());
						orderedUpdateable.add(info2);
					}
				}
			}
		}


		Collections.sort(orderedUpdateable, new RequiredByComparator());

		ArrayList ports = new ArrayList();
		for (Iterator iterator = orderedUpdateable.iterator(); iterator.hasNext();)
		{
			PortVersionInfo info = (PortVersionInfo) iterator.next();
			PortVersionInfo curinfo = (PortVersionInfo) installedByPortDirs.get(info.getPortDir());
			Hashtable port = new Hashtable();
			port.put("name", info.getName());
			port.put("version", info.getVersion());
			port.put("lastVersion", curinfo.getVersion());
			port.put("requiredBy", info.getRequiredBy());
			port.put("portDir", info.getPortDir());
			ports.add(port);
		}


		Map root = new HashMap();
		root.put("skippedFile", skippedFile);
		root.put("ports", ports);

		Template t = new Configuration().getTemplate(template);

		StringWriter writer = new StringWriter();
		try
		{
			t.process(root, writer);
			if (outFile != null && outFile.equalsIgnoreCase("--stdout"))
			{
				System.out.println(writer.toString());
				System.exit(0);
			}
		}
		catch (TemplateException e)
		{
			throw new Exception("Error while processing FreeMarker template", e);
		}

		FileWriter fileWriter = new FileWriter(new File(outFile));
		fileWriter.write(writer.toString());
		fileWriter.close();
	}

	public static boolean newerVersion(String a1, String b1)
	{
		return cmpVersion(a1, b1) > 0;
	}

	public static int cmpVersion(String a1, String b1)
	{
		//todo think about 1.0a1, 1.0rc2.

		for (Iterator iterator = tokens.iterator(); iterator.hasNext();)
		{
			String s = (String) iterator.next();

			if (a1.contains(s) || b1.contains(s))
			{
				StringTokenizer a = new StringTokenizer(a1, s);
				StringTokenizer b = new StringTokenizer(b1, s);

				int cnt = a.countTokens();
				if (cnt > b.countTokens())
				{
					cnt = b.countTokens();
				}

				for (int i = 0; i < cnt; i++)
				{
					String ant = a.nextToken();
					String bnt = b.nextToken();
					int res = cmpVersion(ant, bnt);
					if (res != 0)
					{
						return res;
					}
				}

				if (a.countTokens() > b.countTokens())
				{
					return -1;
				}

				if (a.countTokens() < b.countTokens())
				{
					return 1;
				}
			}

		}

		try
		{
			Integer b1int = new Integer(b1);
			Integer a1int = new Integer(a1);

			return b1int.compareTo(a1int);

		}
		catch (Exception e)
		{
			//nothing
		}

		return a1.compareTo(b1);
	}

}
